﻿//Copyright 2011 - Jonathan Allen

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Diagnostics.Contracts;
using System.Diagnostics.CodeAnalysis;

namespace Granite.Modeling.Internals
{

	/// <summary>
	/// This is the simplest implementation of PropertyBagBase. It supports normal property-change nofifications and validation events.
	/// </summary>
	public class PropertyBag : PropertyBagBase
	{
		readonly Dictionary<String, object> m_Values = new Dictionary<string, object>(StringComparer.Ordinal);

		/// <summary>
		/// This is the simplest implementation of PropertyBagBase. It supports normal property-change nofifications and validation events.
		/// </summary>
		/// <param name="owner">Pass-through to PropertyBagBase</param>
		public PropertyBag(object owner)
			: base(owner)
		{
			Contract.Requires(owner != null);
		}

		/// <summary>
		/// Implmentors need to override this to return the indicated value.
		/// </summary>
		/// <param name="propertyName">Name of the property to fetch.</param>
		/// <returns>The indicated value or System.Reflection.Missing if the value isn't defined.</returns>
		[Pure]
		public override object GetValue([CallerMemberName] string propertyName)
		{
			if (propertyName == null)
				throw new ArgumentNullException("propertyName", "propertyName is null");
			if (String.IsNullOrEmpty(propertyName))
				throw new ArgumentException("propertyName is empty.", "propertyName");
			Contract.EndContractBlock();

			if (m_Values.ContainsKey(propertyName))
				return m_Values[propertyName];

			return Missing.Value;
		}

		/// <summary>
		/// This property indicates whether or not the assoicated property was created.
		/// </summary>
		/// <param name="propertyName"></param>
		/// <returns></returns>
		[Pure]
		public override bool IsDefined([CallerMemberName] string propertyName)
		{
			if (propertyName == null)
				throw new ArgumentNullException("propertyName", "propertyName is null");
			if (String.IsNullOrEmpty(propertyName))
				throw new ArgumentException("propertyName is empty.", "propertyName");
			Contract.EndContractBlock();

			return m_Values.ContainsKey(propertyName);
		}

		/// <summary>
		/// Implementors need to override this to save the indicated value.
		/// </summary>
		/// <param name="value">The value to be saved. A null will set the value to null. System.Reflection.Missing will delete the field entirely.</param>
		/// <param name="mode">Indicates special handling for the action. Ignores SetAsOriginal.</param>
		/// <param name="propertyName">Name of property to update</param>
		/// <remarks>This will create the property if it doesn't already exist</remarks>
		public override bool Set(object value, PropertySetModes mode, [CallerMemberName] string propertyName)
		{
			if (propertyName == null)
				throw new ArgumentNullException("propertyName", "propertyName is null");
			if (String.IsNullOrEmpty(propertyName))
				throw new ArgumentException("propertyName is empty.", "propertyName");
			Contract.EndContractBlock();

			var property = Metadata.Properties[propertyName];

			if (mode.HasFlag(PropertySetModes.FixCasing))
				propertyName = property.Name;

			var oldValue = GetValue(propertyName);

			//if (mode.HasFlag(PropertySetModes.ParseType))
			//	value = ParseType((string)value, propertyName);

			if (Object.Equals(oldValue, value))
				return false;

			m_Values[propertyName] = value;

			if (mode.HasFlag(PropertySetModes.RaiseChangedEvent))
				OnPropertyChanged(property);

			if (mode.HasFlag(PropertySetModes.ValidateProperty))
				OnRevalidateProperty(property);

			if (mode.HasFlag(PropertySetModes.ValidateObject))
				OnRevalidateObject();

			return true;
		}

		[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode"), SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
		[ContractInvariantMethod]
		void ObjectInvariant()
		{
			Contract.Invariant(m_Values != null);
		}

	}

}
